/**
 * Copyright: Copyright (c) 2018, 2022
 * Company: 杭州叙简科技股份有限公司
 */
package cn.com.scooper.aop.log;

import cn.com.scooper.common.resp.APIRespJson;
import cn.com.scooper.common.constant.CommonConstant;
import cn.com.scooper.pojo.core.AccountBo;
import cn.com.scooper.pojo.log.LogOperatePo;
import cn.com.scooper.pojo.log.LogOperateVo;
import cn.com.scooper.service.core.CoreService;
import cn.com.scooper.service.log.LogOperateService;
import cn.com.scooper.utils.HttpServletUtils;
import cn.com.scooper.utils.JacksonUtils;
import eu.bitwalker.useragentutils.UserAgent;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Method;
import java.util.*;

/**
 * 操作日志切面类
 *
 * @author LuLihong
 * @date 2018-08-23
 **/
@Aspect
@Component
public class OperateLogAspect {
    private static Logger log = LoggerFactory.getLogger(OperateLogAspect.class);

    private static final String HEADER_USER_AGENT = "User-Agent";
    private static final String PROJECT_KEY = "apics";

    private static final String BRACKET_LEFT = "{";
    private static final String BRACKET_RIGHT = "}";

    /**
     * 显示title最大长度
     */
    private static final int TITLE_LEN_SHOW_MAX = 100;

    private static final Byte RESULT_SUCC = 0x0;
    private static final Byte RESULT_FAIL = 0x1;

    @Autowired
    private LogOperateService logOperateService;

    /**
     * 使用增强处理来记录操作详细日志
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    @Around("execution(* cn.com.scooper.controller.data..*.*(..))")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();

        // 记录访问时间
        LogOperatePo logOperatePo = new LogOperatePo();
        logOperatePo.setGmtCreate(new Date());

        // 类和方法
        String className = joinPoint.getTarget().getClass().getName();
        String methodName = joinPoint.getSignature().getName();

        // 执行方法
        Object returnObj = null;
        int respResult = RESULT_SUCC;
        String respDesc = "";
        String exeException = "";
        Exception catchException = null;
        try {
            // 执行业务
            returnObj = joinPoint.proceed();

            if (returnObj instanceof APIRespJson) {
                APIRespJson respJson = (APIRespJson) returnObj;

                respResult = respJson.getCode() == 0 ? RESULT_SUCC : RESULT_FAIL;
                respDesc = respJson.getMessage();
            }
        } catch (Exception e) {
            catchException = e;
            respResult = RESULT_FAIL;

            log.warn("操作日志AOP：捕获到方法运行异常，方法为" + className + "." + methodName + "()，异常为："
                    + e.getMessage() );
            exeException = parseException(e);
        }

        // 解析和保存操作日志
        try {
            // 获取方法对象
            Method exeMethod = null;

            Signature signature = joinPoint.getSignature();
            if (signature instanceof MethodSignature) {
                // 方法参数类
                Class[] parameterTypes = ((MethodSignature) signature).getParameterTypes();
                // 获取方法实体
                exeMethod = joinPoint.getTarget().getClass().getMethod(methodName, parameterTypes);
            }

            // 如果不存在切入方法 或者切入方法没有使用OperateLog注解，就不需要记录日志
            if (exeMethod == null || !exeMethod.isAnnotationPresent(OperateLog.class)) {
                return returnObj;
            }

            // 获取HttpServletRequest
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
                    .getRequest();

            // 用户信息
            AccountBo accountBo = getReqAccount(request);
            logOperatePo.setUserId(accountBo.getId());
            logOperatePo.setUserName(accountBo.getAccShowname());

            // HTTP相关
            String remoteAddr = HttpServletUtils.getRealIPFromNginx(request);
            logOperatePo.setUserIp(remoteAddr);
            UserAgent userAgent = UserAgent.parseUserAgentString(request.getHeader(HEADER_USER_AGENT));
            logOperatePo.setUserOs(userAgent.getOperatingSystem().toString());
            logOperatePo.setUserBrowser(userAgent.getBrowser() + "-" + userAgent.getBrowserVersion());

            logOperatePo.setRequsetUri(request.getRequestURI());
            logOperatePo.setHttpMethod(request.getMethod());

            // 执行对象
            logOperatePo.setExeCodeClass(className);
            logOperatePo.setExeCodeMethod(methodName);

            // 日志标题
            String title = exeMethod.getAnnotation(OperateLog.class).content();
            title = refreshTitle(title, request);
            logOperatePo.setLogTitle(title);

            // 响应
            logOperatePo.setRespResult(respResult == 0 ? RESULT_SUCC : RESULT_FAIL);
            logOperatePo.setRespDesc(respDesc);
            long usedTimeLen = System.currentTimeMillis() - startTime;
            logOperatePo.setRespTimeLen((int)usedTimeLen);
            logOperatePo.setProjectKey(PROJECT_KEY);

            // 请求和异常
            String requestParam = parseRequestParam(request);
            Map<String, String> extAttr = new HashMap<>(2);
            extAttr.put("request_parm", requestParam);
            extAttr.put("exe_exception", exeException);
            
            // 保存
            LogOperateVo logOperateVo = new LogOperateVo();
            logOperateVo.setLogOperatePo(logOperatePo);
            logOperateVo.setExtAttr(extAttr);

            logOperateService.saveLogOperate(logOperateVo);
        } catch (Exception e) {
            log.warn("操作日志AOP：解析切入方法失败，方法为" + className + "." + methodName + "()" );
        } finally {
            if (catchException != null) {
                throw catchException;
            }
        }

        return returnObj;
    }

    /**
     * 解析请求参数
     * @param request
     * @return
     */
    private String parseRequestParam(HttpServletRequest request) {
        Map<String, String> map = new HashMap<>();
        Enumeration<String> parameterNames = request.getParameterNames();
        while (parameterNames.hasMoreElements()) {
            String name = parameterNames.nextElement();
            String value = request.getParameter(name);
            map.put(name, value);
        }

        return JacksonUtils.toJSONString(map);
    }

    /**
     * 获取请求的用户信息，从token中获取
     * @param request
     * @return
     */
    protected AccountBo getReqAccount(HttpServletRequest request) {
        String token = HttpServletUtils.getReqParam(request, CommonConstant.P_TOKEN);

        if (token == null) {
            return null;
        }

        return coreService.getAccountByToken(token);
    }



    /**
     * 刷新title，将里面的{name}的字符串替换掉真实的值
     * @param title
     * @param request
     * @return
     */
    private String refreshTitle(String title, HttpServletRequest request) {
        if (!title.contains(BRACKET_LEFT) || !title.contains(BRACKET_RIGHT)) {
            return title;
        }

        Enumeration<String> parameterNames = request.getParameterNames();
        while (parameterNames.hasMoreElements()) {
            String name = parameterNames.nextElement();
            String value = request.getParameter(name);
            String key = BRACKET_LEFT + name + BRACKET_RIGHT;
            if (title.contains(key)) {
                title = title.replace(key, value);
            }
        }

        if (title.length() > TITLE_LEN_SHOW_MAX) {
            title = title.substring(0, TITLE_LEN_SHOW_MAX);
        }
        return title;
    }
    
    /**
     * 解析异常，将异常信息以字符串返回。
     * @param e
     * @return
     */
    private String parseException(Exception e) {
        StringWriter stringWriter;
        PrintWriter printWriter = null;
        try {
            stringWriter = new StringWriter();
            printWriter = new PrintWriter(stringWriter);
            e.printStackTrace(printWriter);
            printWriter.flush();
            stringWriter.flush();
        } finally {
            if (printWriter != null) {
                printWriter.close();
            }
        }

        return stringWriter.toString();
    }

    @Autowired
    protected CoreService coreService;
}
